#include "NetworkQuictunHelper.h"
#include <QApplication>
#include <QThread>
#include <QProcess>
#include <QDir>
#include <QDebug>

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QByteArray>
#include <QJsonValue>
#include <QEventLoop>
#include <QJsonArray>
#include <QFile>

NetworkQuictunHelper* NetworkQuictunHelper::mSelf = nullptr;

#ifdef Q_OS_WIN
#include <windows.h>
class QuictunProcessThread : public QThread
{
public:
    using QThread::QThread;

    void setCommand(const QString &command) {
        mCommand = command;
    }

protected:
    //void run() override {
    //    AllocConsole();
    //    ShowWindow(GetConsoleWindow(), SW_HIDE);
    //    system(mCommand.toLocal8Bit());
    //}

private:
    QString mCommand;
};

bool killQuictunViewProcess()
{
    return QProcess::startDetached("taskkill -f -im quictun-windows-amd64.exe");
}
#endif

NetworkQuictunHelper::NetworkQuictunHelper(QObject *parent)
    : QObject(parent)
    #ifdef Q_OS_WIN
    , mQuictunThread(new QuictunProcessThread(this))
    #else
    , mQuictunProcess(nullptr)
    #endif
{
#ifdef Q_OS_WIN
    connect(mQuictunThread, &QuictunProcessThread::finished, this, &NetworkQuictunHelper::signalQuictunProcessFinished);
    connect(mQuictunThread, &QuictunProcessThread::finished, this, [=] { mQuictunRunning = false;});

#endif
    startQuictun();
}

void NetworkQuictunHelper::parsePostCreateTunnelback(QByteArray response)
{
    QJsonDocument jsonResponse = QJsonDocument::fromJson(response);
    if (!jsonResponse.isNull()) {
        if(!jsonResponse.isArray()) {

            QJsonObject jsonObject = jsonResponse.object();
            QString localSocketAddr = jsonObject["data"].toObject()["localSocketAddr"].toString();
            QString serverAppAddr = jsonObject["data"].toObject()["serverAppAddr"].toString();
            QString side = jsonObject["data"].toObject()["side"].toString();
            QString uuid = jsonObject["data"].toObject()["uuid"].toString();
            if(!serverAppAddr.contains("9000")) {
                mCreateTunnelReply.mLocalSocketAddr = localSocketAddr;
                mCreateTunnelReply.mServerAppAddr = serverAppAddr;
                mCreateTunnelReply.mSide = side;
                mCreateTunnelReply.mTunnelUuid = uuid;
                qDebug()<<"mCreateTunnelReply.mLocalSocketAddr" << localSocketAddr<<"\n"<<
                          "mCreateTunnelReply.mServerAppAddr" << serverAppAddr<<"\n"<<
                          "mCreateTunnelReply.mSide" <<side<<"\n"<<
                          "mCreateTunnelReply.mTunnelUuid" << uuid;
                Q_EMIT signalPostCreateTunnel(mCreateTunnelReply);
            } else if(serverAppAddr.contains("9000")){
                mPlatFormTunnel.mLocalSocketAddr = localSocketAddr;
                mPlatFormTunnel.mServerAppAddr = serverAppAddr;
                mPlatFormTunnel.mSide = side;
                mPlatFormTunnel.mTunnelUuid = uuid;
                qDebug()<<"mPlatFormTunnel.mLocalSocketAddr" << localSocketAddr<<"\n"<<
                          "mPlatFormTunnel.mServerAppAddr" << serverAppAddr<<"\n"<<
                          "mPlatFormTunnel.mSide" <<side<<"\n"<<
                          "mPlatFormTunnel.mTunnelUuid" << uuid;
                Q_EMIT signalPlatFormTunnel(mPlatFormTunnel);
            }
        } else {
            QJsonArray jsonArray = jsonResponse.array();
            QList<CreateTunnelReply> createTunnelReplyList;
            foreach (const QJsonValue &value, jsonArray) {
                if (value.isObject()) {
                    QJsonObject jsonObject = value.toObject();

                    QString side = jsonObject["data"].toObject()["side"].toString();
                    QString serverAppAddr = jsonObject["data"].toObject()["serverAppAddr"].toString();
                    QString localSocketAddr = jsonObject["data"].toObject()["localSocketAddr"].toString();
                    QString uuid = jsonObject["data"].toObject()["uuid"].toString();
                    CreateTunnelReply reply;
                    reply.mLocalSocketAddr = localSocketAddr;
                    reply.mServerAppAddr = serverAppAddr;
                    reply.mSide = side;
                    reply.mTunnelUuid = uuid;
                    createTunnelReplyList.append(reply);

                    if(!serverAppAddr.contains("9000")) {
                        mCreateTunnelReply = reply;
                        Q_EMIT signalPostCreateTunnel(mCreateTunnelReply);
                    } else if(serverAppAddr.contains("9000")){
                        mPlatFormTunnel = reply ;
                        Q_EMIT signalPlatFormTunnel(mPlatFormTunnel);
                    }
                }
            }
            if(createTunnelReplyList.length() > 0) {
                Q_EMIT signalGetAllTunnels(createTunnelReplyList);
            }
        }
    }
    else {
        qDebug() << "Failed to parse JSON.";
    }
}

QString NetworkQuictunHelper::getAvailablePort()
{
    QString outPut;

    bool available = false;
    static quint16 port = 1080;
    do {
        port += 1;
        QTcpSocket socket;
        socket.connectToHost("127.0.0.1", port);
        if(socket.waitForConnected(100)) {
            socket.disconnectFromHost();
        } else {
            available = true;
        }
    } while (!available);
    outPut = "127.0.0.1:"+QString::number(port);
    qDebug()<<"getAvailablePort"<<outPut<<"_______________";
    return outPut;

}

NetworkQuictunHelper *NetworkQuictunHelper::instance()
{
    if (!mSelf)
        mSelf = new NetworkQuictunHelper(qApp);

    return mSelf;
}

NetworkQuictunHelper::~NetworkQuictunHelper()
{
    stopQuictun();
}

void NetworkQuictunHelper::sendPostCreateTunnel(const CreateTunnelRequest &req)
{
    qDebug()<<"sendPostCreateTunnel------------------------------------------";
    QNetworkAccessManager manager;
    QJsonObject jsonObject;
    jsonObject["gatewayAddr"] = req.mGatewayAddr;
    jsonObject["serverAppAddr"] = req.mServerAppAddr;
    jsonObject["localSocketAddr"] = getAvailablePort();

    QJsonDocument jsonDoc(jsonObject);
    QByteArray jsonData = jsonDoc.toJson();

    QNetworkRequest request(QUrl(QString("http://%1/createTunnel").arg(mAvailablePort)));

    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    request.setHeader(QNetworkRequest::ContentLengthHeader, QByteArray::number(jsonData.size()));

    QNetworkReply *reply = manager.post(request, jsonData);


    QEventLoop loop;

    QObject::connect(reply, &QNetworkReply::finished, [&]() {
        qDebug()<<"state:        "<<reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

        if (reply->error() == QNetworkReply::NoError) {
            QByteArray response = reply->readAll();
            qDebug()<<"sendPostCreateTunnel"<<"=================="<<response;
            parsePostCreateTunnelback(response);


        } else {
            qDebug() << "Error detected: " << reply->errorString();
            Q_EMIT signalTransferError();
        }
        reply->deleteLater();
        loop.quit();
    });
    loop.exec();
    manager.deleteLater();
}

void NetworkQuictunHelper::sendPutUpdateTunnel(const QString &req)
{
    if(mOldRequest == req) {
        signalPostCreateTunnel(mCreateTunnelReply);
        return;
    }

    mOldRequest = req;
    qDebug()<<"sendPutUpdateTunnel------------------------------------------";
    QNetworkAccessManager manager;
    QJsonObject jsonObject;
    jsonObject["serverAppAddr"] = req;

    QJsonDocument jsonDoc(jsonObject);
    QByteArray jsonData = jsonDoc.toJson();
    QNetworkRequest request(QUrl(QString("http://%1/updateTunnel/%2").arg(mAvailablePort).arg(mCreateTunnelReply.mTunnelUuid)));

    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    request.setHeader(QNetworkRequest::ContentLengthHeader, QByteArray::number(jsonData.size()));

    QNetworkReply *reply = manager.put(request, jsonData);


    QEventLoop loop;

    QObject::connect(reply, &QNetworkReply::finished, [&]() {

        if (reply->error() == QNetworkReply::NoError) {
            QByteArray response = reply->readAll();
            qDebug()<<"sendPutUpdateTunnel-=--------------------"<<response;
            parsePostCreateTunnelback(response);


        } else {
            qDebug() << "Error detected: " << reply->errorString();
            Q_EMIT signalTransferError();
        }
        reply->deleteLater();
        loop.quit();
    });
    loop.exec();
    manager.deleteLater();
}

void NetworkQuictunHelper::deleteAllTunnels()
{
    QNetworkAccessManager manager;
    QNetworkRequest request(QUrl(QString("http://%1/deleteTunnel/%2").arg(mAvailablePort).arg(mCreateTunnelReply.mTunnelUuid)));

    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    QNetworkReply *reply = manager.deleteResource(request);


    QEventLoop loop;

    QObject::connect(reply, &QNetworkReply::finished, [&]() {

        if (reply->error() == QNetworkReply::NoError) {
            qDebug() << "DELETE request successful";
            mCreateTunnelReply.clear();
            stopQuictun();
            startQuictun();
        } else {
            qDebug() << "Error detected: " << reply->errorString();
            Q_EMIT signalTransferError();
        }
        reply->deleteLater();
        loop.quit();
    });
    loop.exec();
    manager.deleteLater();
}

bool NetworkQuictunHelper::getAllTunnels()
{
    bool createTunnel = false;
    QNetworkAccessManager manager;
    QNetworkRequest request(QUrl(QString("http://%1/allTunnels").arg(mAvailablePort)));

    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    QNetworkReply *reply = manager.get(request);


    QEventLoop loop;

    QObject::connect(reply, &QNetworkReply::finished, [&]() {

        if (reply->error() == QNetworkReply::NoError) {
            QByteArray response = reply->readAll();

            QJsonDocument jsonResponse = QJsonDocument::fromJson(response);

            QJsonObject jsonObj = jsonResponse.object();
            QJsonValue dataValue = jsonObj["data"];
            qDebug() << "request successful"<<response<<"********************************";
            if(dataValue.isArray() && !dataValue.toArray().isEmpty()) {
                if(!mCreateTunnelReply.mLocalSocketAddr.isEmpty())
                    createTunnel = true;
            }

        } else {
            qDebug() << "Error detected: " << reply->errorString();
            Q_EMIT signalTransferError();
        }
        reply->deleteLater();
        loop.quit();
    });
    loop.exec();
    manager.deleteLater();
    return createTunnel;
}

void NetworkQuictunHelper::stopQuictun()
{
    qDebug()<<"NetworkQuictunHelper::stopQuictun________________________________";
#ifdef Q_OS_WIN
    killQuictunViewProcess();
    bool blocked = mQuictunThread->blockSignals(true);
    mQuictunThread->terminate();
    mQuictunThread->blockSignals(blocked);
#else
#ifdef Q_OS_LINUX

    if (!mQuictunProcess)
        return;

    if (mQuictunProcess->state() == QProcess::NotRunning)
        return;

    // bool wasBlocked = mQuictunProcess->blockSignals(true);
    mQuictunProcess->kill();
    mQuictunProcess->waitForFinished();
    //    mQuictunProcess->blockSignals(wasBlocked);
    mQuictunProcess->deleteLater();
    mQuictunProcess = nullptr;
#endif
#endif
    mQuictunRunning = false;
    mCreateTunnelReply.clear();
    mPlatFormTunnel.clear();
}

void NetworkQuictunHelper::startQuictun()
{
    if(mQuictunRunning) return;
    QDir::setCurrent(qApp->applicationDirPath() + QChar('/'));
    // + QLatin1String("bin"));

    mAvailablePort = getAvailablePort();


    QString argument(QString("--api-listen-addr %1").arg(mAvailablePort));
#ifdef Q_OS_WIN
    QFile file(qApp->applicationDirPath() +"/"+ "quictun-windows-amd64.exe");
    qDebug()<<file.fileName();
    if (!file.exists()) {
        qDebug()<<"-------------------file can't find---------------------"<<"\n"
               <<file.fileName();
        return;
    }
    mQuictunThread->setCommand(QLatin1String("quictun-windows-amd64.exe ") + argument);
    qDebug() << "command: " << QLatin1String("quictun-windows-amd64.exe ") << argument;
    mQuictunThread->start();
#else
    mQuictunProcess = new QProcess(this);
    connect(mQuictunProcess,  QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &NetworkQuictunHelper::signalQuictunProcessFinished);
    mQuictunProcess->setWorkingDirectory(qApp->applicationDirPath());

    QFile fileamd64(qApp->applicationDirPath() +"/"+ "quictun-linux-amd64-ubuntu-2004");
    QFile filearm64(qApp->applicationDirPath() +"/"+ "quictun-linux-arm64");


    if (!fileamd64.exists() && !filearm64.exists()) {
        qDebug()<<"-------------------file can't find---------------------"<<"\n";
        return;
    }

    QString filename = fileamd64.exists() ? "quictun-linux-amd64-ubuntu-2004" : "quictun-linux-arm64";

    QString command = filename + QChar::Space + argument;
    mQuictunProcess->start(command);
    mQuictunProcess->waitForStarted();

#endif
    mQuictunRunning = true;
}

